﻿using System;
using System.Threading;
using System.Threading.Tasks;

/* 使用 Thread 和 CancellationToken 来实现异步线程任务的取消。
 * 
 * ThreadCancellationTask 类中，通过使用 CancellationTokenSource 创建一个可以自动取消的任务。
 * 
 * FooAsync 方法模拟了一个长时间执行的非常见阻塞的耗时操作，用于演示如何将这种类型的操作取消。
 * 
 * 
 * CancelableThreadTask 类封装了一个可取消的线程任务。
 * 
 * 该任务可以通过 CancellationToken 在外部请求取消。
 * 
 * 调用 token.Register() 注册的回调用于中止线程任务，并设置任务状态为取消。
 */
namespace Demo
{
    public class ThreadCancellationTask
    {
        /// <summary>
        /// 异步方法 Fun，用于演示线程的取消任务
        /// </summary>
        public async void Fun()
        {
            // 创建一个3秒后自动取消的CancellationTokenSource
            var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));

            // 创建一个可取消的线程任务，并传递需要执行的操作FooAsync
            var threadTask = new CancelableThreadTask(FooAsync);

            // 使用SimpleTimer来记录任务开始和执行时间
            using (new SimpleTimer("Task started"))
            {
                try
                {
                    // 启动线程任务，并传递取消令牌
                    var task = threadTask.RunAsync(cts.Token);

                    // 等待任务完成
                    await task;
                }
                catch (OperationCanceledException)
                {
                    // 捕获任务取消异常，并打印取消信息
                    Console.WriteLine("Task was canceled");
                }
            }
        }

        /// <summary>
        /// 模拟的任务方法 FooAsync
        /// </summary>
        void FooAsync()
        {
            try
            {
                // 打印任务开始信息
                Console.WriteLine("Foo start...");

                // 模拟耗时操作，等待5秒，这种阻塞无法使用 CancelableThreadTask 取消
                //await Task.Delay(5000);

                // 模拟耗时操作，等待5秒，这种阻塞可以使用 CancelableThreadTask 取消
                //Thread.Sleep(5000);

                // 模拟耗时操作，耗时6秒，这种阻塞可以使用 CancelableThreadTask 的 Abort 取消，Interrupt不能取消
                long sum = 0;
                for (long i = 0; i < 3_000_000_000; i++)
                {
                    sum++;
                }

                // 打印任务结束信息
                Console.WriteLine("Foo end...");
            }
            catch (Exception)
            {
                // 捕获任何异常并打印任务取消信息
                Console.WriteLine("Foo cancelled...");
            }
        }
    }

    public class CancelableThreadTask
    {
        /// <summary>
        /// 用于表示线程的实例
        /// </summary>
        private Thread _thread;
        /// <summary>
        /// 表示任务完成状态的TaskCompletionSource对象
        /// </summary>
        private TaskCompletionSource<object> _tcs;

        /// <summary>
        /// 保存要执行的任务操作
        /// </summary>
        private readonly Action _action;
        /// <summary>
        /// 可选的异常处理回调
        /// </summary>
        private readonly Action<Exception> _onError;
        /// <summary>
        /// 可选的任务完成回调
        /// </summary>
        private readonly Action _onCompleted;

        /// <summary>
        /// // 标识任务是否正在运行
        /// </summary>
        private int _isRunning;

        /// <summary>
        /// 构造函数，初始化任务操作和可选回调
        /// </summary>
        /// <param name="action"></param>
        /// <param name="onError"></param>
        /// <param name="onCompleted"></param>
        public CancelableThreadTask(Action action, Action<Exception> onError = null, Action onCompleted = null)
        {
            // 检查任务操作是否为空

            _action      = action ?? throw new ArgumentNullException(nameof(action));
            _onError     = onError;
            _onCompleted = onCompleted;
        }

        /// <summary>
        /// 启动线程并异步运行任务
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        public Task RunAsync(CancellationToken token)
        {
            // 确保任务只会被运行一次
            if (Interlocked.CompareExchange(ref _isRunning, 1, 0) == 1)
                throw new InvalidOperationException("Task is already running!");

            // 初始化任务完成标识
            _tcs = new TaskCompletionSource<object>();

            // 创建并启动新的线程执行任务操作
            _thread = new Thread(() =>
            {
                try
                {
                    // 执行传入的任务操作
                    _action();
                    // 设置任务完成状态
                    _tcs.SetResult(null);
                    // 如果有任务完成回调，则调用它
                    _onCompleted?.Invoke();
                }
                catch (Exception e)
                {
                    // 如果捕获到线程中断异常，尝试设置任务为取消状态
                    if (e is ThreadInterruptedException)
                        _tcs.TrySetCanceled();
                    else
                        // 设置任务的异常状态
                        _tcs.SetException(e);

                    // 如果有错误处理回调，调用回调
                    _onError?.Invoke(e);
                }
                finally
                {
                    // 重置运行标识
                    Interlocked.Exchange(ref _isRunning, 0);
                }
            });

            // 注册取消令牌的回调，以便在请求取消时中止线程
            token.Register(() =>
            {
                // 检查任务是否正在运行，并中止线程
                if (Interlocked.CompareExchange(ref _isRunning, 0, 1) == 1)
                {
                    /* Thread.Abort():
                     * 
                     * 作用：强制终止线程的执行，抛出 ThreadAbortException，允许线程在捕获该异常时进行清理操作，但终究会终止线程。
                     * 
                     * 优势：可以立即终止任何状态下的线程，无论它是否处于阻塞状态。
                     * 
                     * 劣势：会强制中断线程的执行，导致状态可能不一致，且无法保证线程安全，容易产生问题，使用风险较高。
                     */

                    /* Thread.Interrupt():
                     * 
                     * 作用：中断线程的执行，但只有当线程处于阻塞状态时（如 Thread.Sleep()、Wait() 等）才会生效，并抛出 ThreadInterruptedException
                     * 
                     * 优势：更温和，不会立即终止线程，只在线程阻塞时生效，线程有机会处理中断请求
                     * 
                     * 劣势：如果线程不处于阻塞状态，Interrupt 无法立即中断，线程继续执行至下一个阻塞点才会生效。
                     */

                    // 立即终止线程
                    _thread.Abort();

                    // 中断线程，不会直接终止，需要线程处于阻塞状态才能有效
                    //_thread.Interrupt();

                    // 确保线程执行完成
                    _thread.Join();
                    // 设置任务为取消状态
                    _tcs.TrySetCanceled(token);
                }
            });

            // 启动线程
            _thread.Start();

            // 返回表示任务的Task
            return _tcs.Task;
        }
    }
}
